﻿-------------------------------------------------------------------------------
-- Addon - Core - Init
-------------------------------------------------------------------------------
-- Version
local MAJOR = "Avion2"
local MINOR = tonumber(("@project-revision@"):match("%d+")) or 0

-- Dependencies
if not LibStub 				  			then error(MAJOR .. " requires LibStub.") 				end
if not LibStub("AceAddon-3.0")  		then error(MAJOR .. " requires AceAddon-3.0.") 			end
if not LibStub("AceDB-3.0")  			then error(MAJOR .. " requires AceDB-3.0.") 			end
if not LibStub("AceDBOptions-3.0") 		then error(MAJOR .. " requires AceDBOptions-3.0.")		end
if not LibStub("AceConfig-3.0")  		then error(MAJOR .. " requires AceConfig-3.0.") 		end
if not LibStub("AceConfigDialog-3.0")  	then error(MAJOR .. " requires AceConfigDialog-3.0.") 	end
if not LibStub("AceSerializer-3.0")		then error(MAJOR .. " requires AceSerializer-3.0.")		end

-- Create
if (LibStub("LibSink-2.0")) then
	Avion2Core = LibStub("AceAddon-3.0"):NewAddon(MAJOR, "AceSerializer-3.0", "LibSink-2.0")
else
	Avion2Core = LibStub("AceAddon-3.0"):NewAddon(MAJOR, "AceSerializer-3.0")
end
Avion2Core.major = MAJOR
Avion2Core.minor = MINOR

-- Set default mixins for modules
Avion2Core:SetDefaultModuleLibraries("AceSerializer-3.0")


-------------------------------------------------------------------------------
-- Addon - Core - Variables
-------------------------------------------------------------------------------
-- Ace libs (that don't need to be embeded)
local AceDB				= LibStub("AceDB-3.0")
local AceDBOpts			= LibStub("AceDBOptions-3.0")
local AceConfig			= LibStub("AceConfig-3.0")
local AceConfigDialog	= LibStub("AceConfigDialog-3.0")

-- Animation core
local LibAvion 			= LibStub("LibAvion-2.0")
local LibAvionOptions 	= LibStub("LibAvionOptions-2.0")

-- Translation
local L 				= LibStub("AceLocale-3.0"):GetLocale(MAJOR)

-- Media DB
Avion2Core.media 		= {
	textureList 			= {},		-- Textures ["name"] = "path"
	functionList 			= {},		-- Functions [function] = "name" (name will be saved)
	pointerList 			= {},		-- Functions ["name"] = function (name will be saved)
}

-- Default animation table
local defaultAnimationTable = {
	init 					= {
		alpha 				= 0.75,
		color 				= {
			r				= 1,
			g 				= 0,
			b 				= 0,
		},
		position 			= {
			x 				= 0,
			y 				= 0,
		},
		scale				= 1,
		rotation 			= 0,
		misc				= {
			duration		= 0,
		},
	},
	anim 					= {
		alpha 				= 1,
		color 				= {
			r 				= 0,
			g 				= 1,
			b 				= 0,
		} ,
		position 			= {
			x 				= 0,
			y 				= 100,
		},
		scale 				= 1.5,
		rotation 			= 0,
		misc				= {
			duration		= 2,
			isAnimated 		= {
				alpha 		= true,
				color 		= true,
				position 	= true,
				scale 		= true,
				rotation 	= true,
			},
			addRandom 		= {
				alpha 		= 0,
				color 		= 0,
				position 	= 0,
				scale 		= 0,
				rotation 	= 0,
			},
			animationFuncName	= {
				alpha 		= "Linear",
				color 		= "Linear",
				position 	= "Linear",
				scale 		= "Linear",
				rotation 	= "Linear",
			},
		},
	},
	fade 			= {
		color 			= {
			r 			= 0,
			g 			= 0,
			b 			= 1,
		},
		position 		= {
			x 			= 0,
			y 			= 0,
		},
		scale 			= 0.5,
		rotation 		= 0,
		misc			= {
			duration		= 1.5,
			isAnimated 		= {
				color 			= true,
				position 		= true,
				scale 			= true,
				rotation 		= true,
			},
			addRandom 		= {
				alpha 			= 0,
				color 			= 0,
				position 		= 0,
				scale 			= 0,
				rotation 		= 0,
			},
			animationFuncName	= {
				alpha 			= "Linear",
				color 			= "Linear",
				position 		= "Linear",
				scale 			= "Linear",
				rotation 		= "Linear",
			},
		},
	},
	misc 			= {
		size 			= {
			width 			= 200,
			height 			= 200,
		},
		fadeMode		= 0,
		showWhileWait 	= false,
		textureFile		= "Interface\\AddOns\\Avion2\\Icons\\Icon",
		mirror 		= {
			x 			= false,
			y 			= false,
		},
		noEnd 		= false,
	},
}


-------------------------------------------------------------------------------
-- Library - Utility functions
-------------------------------------------------------------------------------
-- Create a deep copy of table (Keep same metatable)
local function deepcopy(object)
	local lookup_table = {}
	
	local function _copy(object)
		if type(object) ~= "table" then
			return object
		elseif lookup_table[object] then
			return lookup_table[object]
		end
		
		local new_table = {}
		lookup_table[object] = new_table
		
		for index, value in pairs(object) do
			new_table[_copy(index)] = _copy(value)
		end
		
		return setmetatable(new_table, getmetatable(object))
	end
	
	return _copy(object)
end


-------------------------------------------------------------------------------
-- Addon - Core - Module prototype
-------------------------------------------------------------------------------
-- Prototype table
Avion2Core.modulePrototype = {}

-- Say Hello to core
function Avion2Core.modulePrototype:OnInitialize()
	-- If module loads for the first time (== nil) enable it (=true)
	if (Avion2Core.db.profile.modules[self.major] == nil) then
		Avion2Core.db.profile.modules[self.major] = true
	end
	
	-- Register module save DB
	self:RegisterModuleDB()
	
	-- Load animation tables from db (convert them)
	self:LoadAnimationTables()
end


-- Enable event-listerning when enabling module
function Avion2Core.modulePrototype:OnEnable()
	-- Disable again if not enabled by Avion2Core
	if (not Avion2Core.db.profile.modules[self.major]) then
		self:Disable()
	else
		-- Register events to listen to
		self:UpdateEventRegistration()
		
		-- Add/Update option table
		Avion2Core:UpdateModuleOptionTable(self)
	end
end


-- Disable event-listerning when disabling module
function Avion2Core.modulePrototype:OnDisable()
	-- Unregister events
	self:UpdateEventRegistration()
	
	-- Update option table
	Avion2Core:UpdateModuleOptionTable(self)
end


-- Disable event-listerning when disabling module
function Avion2Core.modulePrototype:GetIconPath()
	-- Where is module?
	if (debugstack(1,2,0):find("\\Avion2\\" .. self.major:sub(8))) then
		return "Interface\\AddOns\\Avion2\\" .. self.major:sub(8) .. "\\Icons\\"
	else
		return "Interface\\AddOns\\" .. self.major .. "\\Icons\\"
	end
end


-- Set as default prototype
Avion2Core:SetDefaultModulePrototype(Avion2Core.modulePrototype)


-------------------------------------------------------------------------------
-- Addon - Core - Common
-------------------------------------------------------------------------------
-- Database with saved variables
function Avion2Core:OnInitialize()
	-- Register database
	self.db = AceDB:New(MAJOR.."DB")
	self.db:RegisterDefaults({
		profile = {
			modules				= {},
			advancedOptions		= false,
			lastMinor 			= MINOR,
		},
	})
	
	-- Setup LibAvionOptions
	LibAvionOptions:PathToIcons("Interface\\AddOns\\Avion2\\Icons\\")
	
	-- Default/Main options
	self.options = {
		type = 'group',
		name = L["AddonName"],
		desc = L["AddonName_Desc"],
		icon = "Interface\\AddOns\\Avion2\\Icons\\Icon",
		args = {
			modules = {
				type = 'group',
				name = L["Modules"],
				desc = L["Modules_Desc"],
				args = {},
			},
			advanced = {
				name = L["Advanced"],
				desc = L["Advanced_Desc"],
				type = "toggle",
				get = function(info) 
					return self.db.profile.advancedOptions
				end,
				set = function(info,val) 
					self.db.profile.advancedOptions = val
					self:UpdateOptionTable()
				end,
			},
			sink = self:GetSinkAce3OptionsDataTable(),
		},
	}

	-- Save Sink options to DB and change Sink options
	self.options.args.sink.name = L["SinkOutput"]
	self.options.args.sink.desc = L["SinkOutput_Desc"]
	self:SetSinkStorage(self.db.profile)
	
	-- Add profile handling for AceConfig-3.0
	self.options.args.profile = AceDBOpts:GetOptionsTable(self.db)
	self.options.args.profile.order = -1
	self.options.args.modules.order = -2
	
	-- Register options to AceConfig-3.0
	AceConfig:RegisterOptionsTable(MAJOR, self.options)
	AceConfig:RegisterOptionsTable(MAJOR .. "_OpenOptions", {
		type = 'group',
		name = L["AddonName"],
		desc = L["AddonName_Desc"],
		icon = "Interface\\AddOns\\Avion2\\Icons\\Icon",
		args = {
			config = {
				type = 'execute',
				name = L["OpenOptions"],
				desc = L["OpenOptions_Desc"],
				icon = "Interface\\AddOns\\Avion2\\Icons\\Icon",
				func = function() 
					AceConfigDialog:Open(MAJOR) 
				end,
			},
		},
	}, "avion2")
	AceConfigDialog:AddToBlizOptions(MAJOR .. "_OpenOptions", L["AddonName"])
	
	-- Register events that are fired on profile change
	self.db.RegisterCallback(self, "OnNewProfile", 		"ProfileChanges")
	self.db.RegisterCallback(self, "OnProfileReset", 	"ProfileChanges")
	self.db.RegisterCallback(self, "OnProfileChanged", 	"ProfileChanges")
	self.db.RegisterCallback(self, "OnProfileCopied", 	"ProfileChanges")
end


-- Enable everything
function Avion2Core:OnEnable()
	-- If any Module available, register its events
	for moduleName, module in self:IterateModules() do
		-- Test if it should be enabled or not
		if (self.db.profile.modules[moduleName]) then
			if (not module:IsEnabled()) then
				module:Enable()
			end
			
		-- Test if it is (for some reason) allready enabled (disable since it shouldn't be!)
		elseif (module:IsEnabled()) then
			module:Disable()
		end
	end
	
	-- Update optiontable once	
	self:UpdateOptionTable()
	
	-- Register menu request (by guideFrame)
	LibAvionOptions.RegisterCallback(self, "LibAvion_MenuRequest", "LibAvion_MenuRequest")
	LibAvionOptions.RegisterCallback(self, "LibAvion_GUIUpdate", "LibAvion_GUIUpdate")
	LibAvionOptions.RegisterCallback(self, "LibSharedMedia_Registered", "LibSharedMedia_Registered")
	
	-- Add a default function
	if (not self.media.pointerList["Linear"]) then
		self:AddFunction("Linear", "return param")
	end
end


-- Disable everything
function Avion2Core:OnDisable()
	-- Stop all animations
	LibAvion:StopAllAnimations(false)
	
	-- Unregister all events
	for _, module in self:IterateModules() do
		if (module:IsEnabled()) then
			module:Disable()
		end
	end
	
	-- Unregister menu event
	LibAvionOptions.UnregisterCallback(self, "LibAvion_MenuRequest", "LibAvion_MenuRequest")
	LibAvionOptions.UnregisterCallback(self, "LibAvion_GUIUpdate", "LibAvion_GUIUpdate")
	LibAvionOptions.UnregisterCallback(self, "LibSharedMedia_Registered")
end


-------------------------------------------------------------------------------
-- Addon - Core - Options
-------------------------------------------------------------------------------
-- Update complete option table
function Avion2Core:UpdateOptionTable()
	if (self.options) then
		local moduleL
		for _, module in self:IterateModules() do
			moduleL = LibStub("AceLocale-3.0"):GetLocale(module.major, true)
			
			-- Options to enable/disable module
			self.options.args.modules.args[module.major] = {
				type = 'toggle',
				name = moduleL["ModuleName"],
				desc = L["EnableModule_Desc"],
				get = function()
					return (self.db.profile.modules[module.major] == true)
				end,
				set = function()
					-- Change enabled status in core
					self.db.profile.modules[module.major] = not self.db.profile.modules[module.major]
					
					-- Change enabled status of module itsself
					if (self.db.profile.modules[module.major]) then
						module:Enable()
					else
						module:Disable()
					end
				end,
			}
		end
				
		-- Update module options
		self:UpdateAllModulesOptionTable()
	end
end


-- Update modules option table
function Avion2Core:UpdateModuleOptionTable(module)
	-- Make sure module exists
	if (self:GetModule(module.major, true)) then
		-- Feed option table into the core
		module:UpdateOptionTable(self.options.args)
	end
end


-- Update option table for all modules
function Avion2Core:UpdateAllModulesOptionTable()
	for _, module in self:IterateModules() do
		self:UpdateModuleOptionTable(module)
	end
end


-- User changed profile,
function Avion2Core:ProfileChanges()
	-- Stop all animations
	LibAvion:StopAllAnimations(false)
	
	-- Update option table
	self:UpdateOptionTable()
end


-- New sounds or fonts?
function Avion2Core:LibSharedMedia_Registered(event, mediatype, key)
	-- Update options on new sound/font
	if (mediatype == LibSharedMedia.MediaType.FONT or mediatype == LibSharedMedia.MediaType.SOUND) then
		self:UpdateOptionTable()
	end
end


-- Menu was requested by a GuideFrame
function Avion2Core:LibAvion_MenuRequest(animationID, frame)
	-- Ignoring animationID for nw (since there is no AceDropdownConfig-3.0
	
	-- Open default config
	LibStub("AceConfigDialog-3.0"):Open(MAJOR)
	
	return true
end


-- Update options
function Avion2Core:LibAvion_GUIUpdate(animationID)
	self:UpdateAllModulesOptionTable()
	LibStub("AceConfigRegistry-3.0"):NotifyChange(MAJOR)
end


-- Create default options for creating new animation
function Avion2Core:GetDefaultNewAnimationOption(db, module)
	if (module) then
		return {
			order = 1,
			type = 'execute',
			name = L["NewAnimation"],
			desc = L["NewAnimation_Desc"],
			func = function()
				local animationTable = deepcopy(defaultAnimationTable)
				
				table.insert(db, animationTable)
				self:LoadAnimationTables(db)
				
				self:UpdateOptionTable()
				module:UpdateEventRegistration()
				
				LibAvionOptions:ShowGuideFrame("Avion2Core_GuideFrame", animationTable)
			end
		}
	else
		return {
			order = 1,
			type = 'execute',
			name = L["NewAnimation"],
			desc = L["NewAnimation_Desc"],
			func = function()
				table.insert(db, deepcopy(defaultAnimationTable))
				self:LoadAnimationTables(db)
				
				self:UpdateOptionTable()
				
				LibAvionOptions:ShowGuideFrame("Avion2Core_GuideFrame")
			end
		}
	end
end


-- Create default options for animation
function Avion2Core:GetDefaultAnimationOption(db, animationListID, animationID, module)
	-- Default options as supplied by LibAvionOptions-2.0
	local options = LibAvionOptions:Ace3Options(animationID, db[animationListID], self.media.textureList, self.media.functionList, self.db.profile.advancedOptions)
	
	-- Inject Sink options into table
	options.text.args.toSink = {
		order 		= 1,
		type 		= "toggle",
		name 		= L["ToSink"],
		desc 		= L["ToSink_Desc"],
		get 		= function()
			return db[animationListID].text and db[animationListID].text.toSink or false
		end,
		set 		= function(info, val)
			db[animationListID].text.toSink = val
		end,
	}	
	
	-- Test animation by triggering it
	options["testAnimation"] = {
		order = -2,
		type = 'execute',
		name = L["RunAnimation"],
		desc = L["RunAnimation_Desc"],
		func = function()
			LibAvion:StopAnimation(animationID, false)
			LibAvion:TriggerAnimation(animationID, db[animationListID])
		end
	}
	
	-- Delete options
	if (module) then
		options["delete"] = {
			order = -1,
			type = 'execute',
			name = L["DeleteAnimation"],
			desc = L["DeleteAnimation_Desc"],
			func = function()
				LibAvion:StopAllAnimations(false)
				LibAvionOptions:HideGuideFrame(animationID)
				LibAvionOptions:HideGuideFrame("Avion2Core_GuideFrame")
				
				table.remove(db, animationListID)
				
				self:UpdateOptionTable()
				module:UpdateEventRegistration()
			end
		}
	else
		options["delete"] = {
			order = -1,
			type = 'execute',
			name = L["DeleteAnimation"],
			desc = L["DeleteAnimation_Desc"],
			func = function()
				LibAvion:StopAllAnimations(false)
				LibAvionOptions:HideGuideFrame(animationID)
				LibAvionOptions:HideGuideFrame("Avion2Core_GuideFrame")
				
				table.remove(db, animationListID)
				
				self:UpdateOptionTable()
			end
		}
	end
	
	return {
		type = 'group',
		name = string.format(L["AnimtionNo"], animationListID),
		desc = L["AnimationNo_Desc"],
		args = options,
		icon = db[animationListID].misc.textureFile,
	}
end


-------------------------------------------------------------------------------
-- Addon - Core - Custom content
-------------------------------------------------------------------------------
-- Add new texture to DB
function Avion2Core:AddTexture(name, path)
	-- Terminate if allready something with that name
	if (self.media.textureList[path]) then
		return false
	end
	
	-- Terminate if already entry with that value
	for _, _name in pairs(self.media.textureList) do
		if (_name == name) then
			return false
		end
	end
	
	-- Add me
	self.media.textureList[name] = path
	
	-- Update options
	self:UpdateOptionTable()
	
	return true
end


-- Add new function to DB
function Avion2Core:AddFunction(name, body)
	-- Terminate if allready something with that name
	if (self.media.functionList[name]) then
		return false
	end

	local func = assert(loadstring([[
		return function (param) 
			]] .. body ..[[
		end
	]]))
	local result = func()

	-- Add me
	self.media.functionList[result] = name
	self.media.pointerList[name] = result
	
	-- New function added, try to update module animations
	for _, module in self:IterateModules() do
		module:LoadAnimationTables()
	end
	
	-- Update options
	self:UpdateOptionTable()
	
	return true
end


-------------------------------------------------------------------------------
-- Addon - Core - Misc
-------------------------------------------------------------------------------
-- Trigger MANY animations at once
function Avion2Core:TriggerAnimationList(triggerID, animationList)
	-- Only trigger animations if enabled
	if (animationList == nil or not self:IsEnabled()) then
		return false
	end
	
	-- Trigger each animation
	for ID, animationTable in pairs(animationList) do
		-- Send to sink (if enabled)
		if (animationTable.text and animationTable.text.toSink) then
			self:Pour(animationTable.text.text, animationTable.text.color.r, animationTable.text.color.g, animationTable.text.color.b, animationTable.text.font, animationTable.text.size, animationTable.text.color.outline)
		end
		
		LibAvion:TriggerAnimation(string.format("%s%d", triggerID, ID), animationTable)
	end
end


-- Stop MANY animations at once
function Avion2Core:StopAnimationList(triggerID, animationList, fadeOut)
	-- Only trigger animations if enabled
	if (animationList == nil) then
		return false
	end
		
	-- Trigger each animation
	for animationID, _ in pairs(animationList) do
		LibAvion:StopAnimation(string.format("%s%d", triggerID, animationID), fadeOut)
	end
end


-- Update animation (convert animationFunc: string name to function pointer)
function Avion2Core:LoadAnimationTables(animationList)
	for _, animationTable in pairs(animationList) do
		LibAvionOptions:ConvertAnimationTable(animationTable, self.media.textureList, self.media.functionList)
	end
end